#include "spl.includes.h"
#include "splib.h"

#define SPL_WINDOW_MINWIDTH   100
#define SPL_WINDOW_MINHEIGHT  50

using namespace SPLib;
using namespace SPLib::Windows;
using namespace SPLib::Geom;

#pragma region Messaging
VOID FrameWnd::wndProc(Event::WndProcEvent& iEvent)
{
  switch(iEvent.msg)
  {
    ////////////////////////////////////////////////////////////////////
    // Window Notifications
  case WM_CLOSE:
    onClose();
    iEvent.handled = TRUE;
    break;
  case WM_WINDOWPOSCHANGED:
    onWindowPosChanged(Event::WindowPosEvent(iEvent.lParam));
    break;
  case WM_WINDOWPOSCHANGING:
    onWindowPosChanging(Event::WindowPosEvent(iEvent.lParam));
    break;
  case WM_SHOWWINDOW:
    onShowWindow(Event::ShowWindowEvent(iEvent.wParam, iEvent.lParam));
    if (mModal && !(BOOL)iEvent.wParam) PostQuitMessage(0);
    break;
  case WM_PARENTNOTIFY:
    onParentNotify(Event::ParentNotifyEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_MOVING:
    onMoving(Event::MovingEvent(iEvent.lParam));
    break;
  case WM_MOVE:
    onMove(Event::MoveEvent(iEvent.lParam));
    break;
  case WM_SIZING:
    onSizing(Event::SizingEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_SIZE:
    onSize(Event::SizeEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_GETMINMAXINFO:
    onGetMinMaxInfo(Event::MinMaxInfoEvent(iEvent.lParam, &iEvent.handled));
    break;
  case WM_ENABLE:
    onEnable(Event::EnableEvent(iEvent.wParam));
    break;
  case WM_ACTIVATEAPP:
    onActivateApp(Event::ActivateAppEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_NCCALCSIZE:
    onNCCalcSize(Event::NCCalcSizeEvent(
      iEvent.wParam, iEvent.lParam, &iEvent.lResult, &iEvent.handled));
    break;
#ifndef COMMON_WM_ONLY
  case WM_QUERYOPEN:
    onQueryOpen(Event::QueryEvent(&iEvent.lResult, &iEvent.handled));
    break;
  case WM_QUERYDRAGICON:
    onQueryDragIcon(Event::QueryDragIconEvent(&iEvent.lResult, &iEvent.handled));
    break;
  case WM_GETICON:
    onGetIcon(Event::GetIconEvent(iEvent.wParam, iEvent.lResult, &iEvent.handled));
    break;
  case WM_EXITSIZEMOVE:
    onExitSizeMove();
    break;
  case WM_ENTERSIZEMOVE:
    onEnterSizeMove();
    break;
#endif
    ////////////////////////////////////////////////////////////////////
    // Mouse Notifications
  case WM_NCLBUTTONDOWN:
    onNCMouseDown(Event::MouseEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_NCLBUTTONUP:
    onNCMouseUp(Event::MouseEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_RBUTTONDOWN:
  case WM_LBUTTONDOWN:
  case WM_MBUTTONDOWN:
  case WM_XBUTTONDOWN:
    // capture mouse events
    SetCapture(getHandle());
    // mouse button pressed
    onMouseDown(Event::MouseEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_LBUTTONUP:
  case WM_RBUTTONUP:
  case WM_MBUTTONUP:
  case WM_XBUTTONUP:
    // capture mouse events
    ReleaseCapture();
    // mouse button released
    onMouseUp(Event::MouseEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_MOUSEHWHEEL:
    onMouseWheel(Event::MouseWheelEvent(Event::Horizontal, iEvent.wParam, iEvent.lParam));
    break;
  case WM_MOUSEWHEEL:
    onMouseWheel(Event::MouseWheelEvent(Event::Vertical, iEvent.wParam, iEvent.lParam));
    break;
  case WM_MOUSEMOVE:
    {
      // mouse event data
      Event::MouseEvent param(iEvent.wParam, iEvent.lParam);

      // if mouse is over window for the first time
      if (!mIsMouseOver)
      {
        // set mouse over flag
        mIsMouseOver = TRUE;

        // setup track mouse event
        TRACKMOUSEEVENT mouseEvent;
        mouseEvent.cbSize = sizeof(mouseEvent);
        mouseEvent.dwFlags = TME_LEAVE;
        mouseEvent.hwndTrack = mHandle;

        // track mouse leave event
        TrackMouseEvent(&mouseEvent);

        // mouse entered window
        onMouseEnter(param);
      }

      // mouse moved over window
      onMouseMove(param);
      break;
    }
  case WM_MOUSELEAVE:
    // mouse leaved window, unset mouse over flag
    mIsMouseOver = FALSE;
    onMouseLeave();
    break;
  case WM_NCHITTEST:
    // calculate client and non-client areas
    onNCHitTest(Event::NCHitTestEvent(iEvent.lParam, &iEvent.lResult, &iEvent.handled));
    break;
    ////////////////////////////////////////////////////////////////////
    // Control Notifications
  case WM_DRAWITEM:
    onDrawItem(Event::DrawItemEvent(iEvent.wParam, iEvent.lParam, &iEvent.handled));
    break;
  case WM_MEASUREITEM:
    onMeasureItem(Event::MeasureItemEvent(iEvent.wParam, iEvent.lParam, &iEvent.handled));
    break;
  case WM_COMPAREITEM:
    onCompareItem(Event::CompareItemEvent(iEvent.wParam, iEvent.lParam, &iEvent.lResult, &iEvent.handled));
    break;
  case WM_NOTIFY:
    iEvent.handled = TRUE;
    onNotify(Event::NotifyEvent(iEvent.wParam, iEvent.lParam, &iEvent.lResult, &iEvent.handled));
    break;
    ////////////////////////////////////////////////////////////////////
    // Keyboard Notifications
  case WM_ACTIVATE:
    onActivate(Event::ActivateEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_KEYDOWN:
    onKeyDown(Event::KeyEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_KEYUP:
    onKeyUp(Event::KeyEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_SYSKEYDOWN:
    onSysKeyDown(Event::KeyEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_SYSKEYUP:
    onSysKeyUp(Event::KeyEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_SETFOCUS:
    onSetFocus(Event::FocusEvent(iEvent.wParam));
    break;
  case WM_KILLFOCUS:
    onKillFocus(Event::FocusEvent(iEvent.wParam));
    break;
    ////////////////////////////////////////////////////////////////////
    // Scroll Bar Notifications
  case WM_HSCROLL:
    onHScroll(Event::ScrollEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_VSCROLL:
    onVScroll(Event::ScrollEvent(iEvent.wParam, iEvent.lParam));
    break;
    ////////////////////////////////////////////////////////////////////
    // Painting & Drawing Notifications
  case WM_PAINT:
    onPaint();
    break;
  case WM_NCPAINT:
    onNCPaint(Event::NCPaintEvent(iEvent.wParam, &iEvent.handled));
    break;
  case WM_ERASEBKGND:
    iEvent.lResult = TRUE;
    onEraseBackground(Event::EraseBackgroundEvent(iEvent.wParam, &iEvent.handled));
    break;
#ifndef COMMON_WM_ONLY
  case WM_PRINT:
    onPrint(Event::PrintEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_PRINTCLIENT:
    onPrintClient(Event::PrintEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_SETREDRAW:
    onSetRedraw(Event::SetRedrawEvent(iEvent.wParam));
    break;
  case WM_SYNCPAINT:
    onSyncPaint();
    break;
#endif
    ////////////////////////////////////////////////////////////////////
    // Menu Notifications
  case WM_CONTEXTMENU:
    onContextMenu(Event::ContextMenuEvent(iEvent.wParam, iEvent.lParam));
    break;
  case WM_MENUCOMMAND:
    onMenuCommand(Event::MenuCommandEvent(iEvent.wParam, iEvent.lParam));
    break;
    ////////////////////////////////////////////////////////////////////
    // Default Messages
  default:
    WndBase::wndProc(iEvent);
    break;
  }
}
#pragma endregion

#pragma region Window Messages
VOID FrameWnd::onDrawItem(Event::DrawItemEvent& iEvent)
{
}
VOID FrameWnd::onMeasureItem(Event::MeasureItemEvent& iEvent)
{
}
VOID FrameWnd::onCompareItem(Event::CompareItemEvent& iEvent)
{
  iEvent.handled = FALSE;
}
VOID FrameWnd::onNotify(Event::NotifyEvent& iEvent)
{
  iEvent.result = NULL;
}
VOID FrameWnd::onPaint()
{
}
VOID FrameWnd::onEraseBackground(Event::EraseBackgroundEvent& iEvent)
{
  iEvent.handled = FALSE;
}
VOID FrameWnd::onNCPaint(Event::NCPaintEvent& iEvent)
{
}
#ifndef COMMON_WM_ONLY
VOID FrameWnd::onDisplayChange(Event::DisplayChangeEvent& iEvent)
{
}
VOID FrameWnd::onPrint(Event::PrintEvent& iEvent)
{
}
VOID FrameWnd::onPrintClient(Event::PrintEvent& iEvent)
{
}
VOID FrameWnd::onSetRedraw(Event::SetRedrawEvent& iEvent)
{
}
VOID FrameWnd::onSyncPaint()
{
}
#endif

VOID FrameWnd::onActivate(Event::ActivateEvent& iEvent)
{
}
VOID FrameWnd::onKeyDown(Event::KeyEvent& iEvent)
{
}
VOID FrameWnd::onKeyUp(Event::KeyEvent& iEvent)
{
}
VOID FrameWnd::onSysKeyDown(Event::KeyEvent& iEvent)
{
}
VOID FrameWnd::onSysKeyUp(Event::KeyEvent& iEvent)
{
}
VOID FrameWnd::onKillFocus(Event::FocusEvent& iEvent)
{
}
VOID FrameWnd::onSetFocus(Event::FocusEvent& iEvent)
{
}
VOID FrameWnd::onContextMenu(Event::ContextMenuEvent& iEvent)
{
}
VOID FrameWnd::onMenuCommand(Event::MenuCommandEvent& iEvent)
{
}
VOID FrameWnd::onMouseEnter(Event::MouseEvent& iEvent)
{
}
VOID FrameWnd::onMouseMove(Event::MouseEvent& iEvent)
{
}
VOID FrameWnd::onMouseDown(Event::MouseEvent& iEvent)
{
}
VOID FrameWnd::onMouseUp(Event::MouseEvent& iEvent)
{
}
VOID FrameWnd::onMouseLeave()
{
}
VOID FrameWnd::onMouseWheel(Event::MouseWheelEvent& iEvent)
{
}
VOID FrameWnd::onNCHitTest(Event::NCHitTestEvent& iEvent)
{
  iEvent.result = 0;
}
VOID FrameWnd::onNCMouseDown(Event::MouseEvent& iEvent)
{
}
VOID FrameWnd::onNCMouseUp(Event::MouseEvent& iEvent)
{
}
VOID FrameWnd::onHScroll(Event::ScrollEvent& iEvent)
{
}
VOID FrameWnd::onVScroll(Event::ScrollEvent& iEvent)
{
}

#ifndef COMMON_WM_ONLY
VOID FrameWnd::onQueryOpen(Event::QueryEvent& iEvent)
{
  iEvent.handled = FALSE;
}
VOID FrameWnd::onQueryDragIcon(Event::QueryDragIconEvent& iEvent)
{
  iEvent.icon = NULL;
}
VOID FrameWnd::onEnterSizeMove(Event::SizeEvent& iEvent)
{
}
VOID FrameWnd::onExitSizeMove(Event::SizeEvent& iEvent)
{
}
HANDLE FrameWnd::onGetIcon(Event::GetIconEvent& iEvent)
{
  return NULL;
}
VOID FrameWnd::onUserChanged()
{
}
#if (WINVER >= 0x0501)
VOID FrameWnd::onThemeChanged()
{
}
#endif
VOID FrameWnd::onStyleChanging(Event::StyleEvent& iEvent)
{
}
VOID FrameWnd::onStyleChanged(Event::StyleEvent& iEvent)
{
}
#endif

VOID FrameWnd::onNCCalcSize(Event::NCCalcSizeEvent& iEvent)
{
  iEvent.handled = FALSE;
}
VOID FrameWnd::onShowWindow(Event::ShowWindowEvent& iEvent)
{
}
VOID FrameWnd::onParentNotify(Event::ParentNotifyEvent& iEvent)
{
}
VOID FrameWnd::onActivateApp(Event::ActivateAppEvent& iEvent)
{
}
VOID FrameWnd::onEnable(Event::EnableEvent& iEvent)
{
}
VOID FrameWnd::onGetMinMaxInfo(Event::MinMaxInfoEvent& iEvent)
{
}
VOID FrameWnd::onWindowPosChanged(Event::WindowPosEvent& iEvent)
{
}
VOID FrameWnd::onWindowPosChanging(Event::WindowPosEvent& iEvent)
{
}
VOID FrameWnd::onMove(Event::MoveEvent& iEvent)
{
}
VOID FrameWnd::onMoving(Event::MovingEvent& iEvent)
{
}
VOID FrameWnd::onSize(Event::SizeEvent& iEvent)
{
}
VOID FrameWnd::onSizing(Event::SizingEvent& iEvent)
{
  adjustMinimumSize(
    SPL_WINDOW_MINWIDTH,
    SPL_WINDOW_MINHEIGHT,
    iEvent.sizeEdge,
    iEvent.rect);
}
VOID FrameWnd::onDestroy()
{
}
VOID FrameWnd::onClose()
{
  if (mModal)
    hide();
  else
    destroy();
}
#pragma endregion

#pragma region Drag & Drop
HRESULT FrameWnd::onGiveFeedback(Event::DragFeedbackEvent& iEvent)
{
  return DRAGDROP_S_USEDEFAULTCURSORS;
}

HRESULT FrameWnd::onQueryContinueDrag(Event::QueryContinueDragEvent& iEvent)
{
  if (iEvent.escapePressed == TRUE)
    return DRAGDROP_S_CANCEL;
  else if (!(iEvent.keyState & MK_LBUTTON))
    return DRAGDROP_S_DROP;
  else
    return S_OK;
}

HRESULT FrameWnd::onDragEnter(Event::DragObjectEvent& iEvent)
{
  *iEvent.effect = FrameWnd::DropTarget::DropEffect(iEvent.keyState,
    *(PPOINTL)((PVOID)iEvent.point), *iEvent.effect);
  return S_OK;
}

HRESULT FrameWnd::onDragOver(Event::DragEvent& iEvent)
{
  *iEvent.effect = FrameWnd::DropTarget::DropEffect(iEvent.keyState,
    *(PPOINTL)((PVOID)iEvent.point), *iEvent.effect);
  return S_OK;
}

HRESULT FrameWnd::onDragLeave() { return S_OK; }

HRESULT FrameWnd::onDrop(Event::DragObjectEvent& iEvent)
{
  *iEvent.effect = DROPEFFECT_NONE;
  return S_OK;
}

BOOL FrameWnd::setIsDropTarget(BOOL fTarget)
{
  // if drop target requested
  if (fTarget) {
    // if drop target class created, destroy it
    if (mDropTarget) delete mDropTarget;

    // initialize Ole
    OleInitialize(0);
    // create and lock drop target class
    CoLockObjectExternal(mDropTarget = new DropTarget(this),
      TRUE, FALSE);

    // register window for drag drop input and return operation state
    return RegisterDragDrop(mHandle, mDropTarget) == S_OK;
  } else {
    // unregister window for drag drop input
    HRESULT hResult = RevokeDragDrop(mHandle);

    // if drop target class created
    if (mDropTarget) {
      // unlock and destroy drop target class
      CoLockObjectExternal(mDropTarget, FALSE, TRUE);
      delete mDropTarget;
      // uninitialize Ole
      OleUninitialize();
    }

    // return operation state
    return hResult == S_OK;
  }
}
#pragma endregion

#pragma region Management
VOID FrameWnd::doClick(UINT uID)
{
  // notification header
  NMHDR header;

  // set notification data
  header.code     = NM_CLICK;
  header.hwndFrom = NULL;
  header.idFrom   = uID;

  // send notification to this window
  SendMessage(*this, WM_NOTIFY, (WPARAM)header.idFrom, (LPARAM)&header);
}

DialogResult FrameWnd::showModal()
{
  if (mModal)
  {
    // already shown as modal window, abort
    return DialogError;
  }
  else
  {
    // window owner
    HWND owner  = getOwner();
    // first window to get focus
    HWND first  = GetWindow(*this, GW_CHILD);
    // last window that got focus
    HWND focus  = GetFocus();
    // window message
    MSG msg;

    // set modal flag
    mModal = TRUE;
    // disable owner window
    EnableWindow(owner, FALSE);
    LockSetForegroundWindow(LSFW_LOCK);
    // display window
    show();

    // find first enabled window that accepts tabstop
    while (!(GetWindowStyle(first) & WS_TABSTOP) || !IsWindowEnabled(first))
    {
      if (!(first = GetWindow(first, GW_HWNDNEXT)))
        break;
    }

    // set first focused window
    SetFocus(first);

    // get window messages
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
      switch (msg.message)
      {
      case WM_KEYDOWN:
        // key pressed, process navigation keys
        switch (Event::KeyEvent(msg.wParam, msg.lParam).vkCode)
        {
        case VK_TAB:
          // set focus to next window
          SetFocus(getNextChild(GetFocus(),
            GetKeyState(VK_SHIFT) >> 1));
          continue;
        case VK_ESCAPE:
          // send cancel message
          doClick(IDCANCEL);
          continue;
        case VK_RETURN:
          // send OK message
          doClick(IDOK);
          continue;
        }
        break;
      }

      // dispatch window message
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

    // enable owner window
    EnableWindow(owner, TRUE);
    SetForegroundWindow(owner);
    // restore focused window
    SetFocus(focus);

    // unset modal flag
    mModal = FALSE;
    return mResult;
  }
}

HWND FrameWnd::getNextChild(HWND start, BOOL previous)
{
  // next focused window
  HWND next = start;

  // find next enabled window that accepts tabstop
  do
  {
    // if no start window specified, start with first window
    if (!start)
    {
      next = start = GetWindow(*this, GW_CHILD);
      continue;
    }

    // if end of the list reached
    if (!(next = GetWindow(next, previous ? GW_HWNDPREV : GW_HWNDNEXT)))
    {
      // continue with the first window
      next = GetWindow(start, previous ? GW_HWNDLAST : GW_HWNDFIRST);
    }

    // if looped, stop
    if (next == start)
      break;
  }
  while (!(GetWindowStyle(next) & WS_TABSTOP) ||
    !IsWindowEnabled(next) || !IsWindowVisible(next));

  // return next window
  return next;
}

#pragma endregion
